
function PropObj 
(
	name,		// required, property name, usually the rightmost property in a property path
	defValue,	// optional, default value for the property, default = ""
	readOnly,	// optional, readonly, value cannot be edited, default == false 
	required,	// optional, does the property require a value (i.e. value != "")
	min,		// optional, the minimum value for the property (for numeric properties only), default == null
	max,		// optional, the maximum value for the property (for numeric properties only), default == null
	rule,		// optional, name of rule to use to validate the property's value before submittal
	label,		// optional, a more friendly name then the name attribute
	restart		// optional, does the server require a restart when this property is changed?
)
{
	if ( ! name ) return null ;
	this.name 		= name ;
	this.defValue	= ( defValue != null ) ? defValue : "" ;
	this.readOnly	= ( readOnly ) ? true : false ;
	this.required	= ( required ) ? true : false ;
	this.min		= ( min != null ) ? parseInt( min ) : null ;
	this.max		= ( max != null ) ? parseInt( max ) : null ;
	this.rule		= ( rule != null ) ? rule : null ;
	this.label		= ( label != null ) ? label : name ;
	this.dirty 		= false ;
	this.ctrlValue	= null ;
	this.m_type		= "Property";
	if ( 0 == this.name.indexOf( "_" ) )
		this.configName( this.name.slice( 1 ) );
	this.bRestart 	= ((restart == null) ? false : restart);	// does the serve require a restart when this prop is changed?
	return this ;
}
PropObj.prototype.copy = function ()
{
	var pObj = new PropObj
	( 
		this.name,
		this.defValue,
		this.readOnly,
		this.required,
		this.min,
		this.max,
		this.rule,
		this.label,
		this.bRestart
	);

	if ( this.m_configName ) pObj.m_configName = this.m_configName ;
	pObj.dirty = this.dirty ;
	pObj.ctrlValue = this.ctrlValue ;
	pObj.value = this.value ;
	return pObj ;
}
PropObj.prototype.toString = function () { return this.name; }
// look for a form field with the same name as the property name, 
// TODO handle exceptions and radio buttons
PropObj.prototype.getCtrl = function ( form )
{
	if ( ! form ) return null;
	var ctrl = form[ this.name ];
	if ( ctrl && ( typeof( ctrl ) == "object" ) && ctrl['type'] )
		return ctrl ;
	else
		return null ;
}
PropObj.prototype.isRequired = function ()
{
	return this.required ;
}
PropObj.prototype.isDirty = function ()
{
	return this.dirty ;
}
PropObj.prototype.getValue = function ()
{
	return ( this.ctrlValue != null ? this.ctrlValue : this.value );
}
PropObj.prototype.setValue = function ( val )
{
	if ( val != this.value )
	{
		this.ctrlValue = val ;
		this.dirty = true ;
	}
}
PropObj.prototype.restartOnChange = function ()
{
	return this.bRestart ;
}
// in cases where the name of the property (which must match a form element name)
// collides with another property name, set this to the right name to use when
// building a paramlist (presumably the colliding names are in different sublists
// so there will be no namespace problems when setting the server config)
// pass a config name to set the property
PropObj.prototype.configName = function ( configName )
{
	if ( configName )
	{
		this.m_configName = configName ;
	}
	return this.m_configName ? this.m_configName : this.name ;
}
PropObj.prototype.setDefault = function ()
{
	if ( isBlank( this.value ) &&
		!isBlank( this.defValue ) )
	{

		this.ctrlValue = this.defValue ;
		this.dirty = true ;
		return true ;
	}
	return false ;
}
function PropList ( name, initVals, propSet, propSetSubLists )
{

	this.m_name = name ;
	this.m_status = null ;
	this.m_parent = null ;
	this.m_props = new Array();
	this.m_subLists = new Array();
	this.m_type = "List";
	this.m_propSet = propSet;
	this.m_propSetSubLists = propSetSubLists;
	// Does the server need a restart? Set to true during BuildParamString() if one or more
	// properties have changed whose bRestart flags are true. Reset to false in Commit()
	this.m_bRestart = false ;
	// Does the server need a restart if this list is deleted?
	this.m_bRestartOnDelete = false ;
	if ( propSet )
		this.initProps( initVals, propSet );
	// setup (randomly named) sublists using propSetSubLists and any nested arrays found in initVals
	if ( initVals && propSetSubLists )
		this.initSubLists( initVals, propSetSubLists );
}	// PropList ()
PropList.prototype.createNew = function ( newName, fCopy )
{
	if ( isBlank( newName ) ) 
		newName = this.m_name ; 

	var newList = new PropList( newName );
	if ( fCopy )
		this.copy( newList );
	return newList;
}	// createNew
PropList.prototype.copy = function ( destList )
{

	destList.m_parent = this.m_parent ;
	destList.m_oldName = "" ;
	destList.m_status = "added" ;
	destList.m_bRestart = this.m_bRestart ;
	destList.m_bRestartOnDelete = this.m_bRestartOnDelete ;
	destList.m_propSet = this.m_propSet ;
	destList.m_propSetSubLists = this.m_propSetSubLists ;
	// make copys of the this list's propObj's
	var prop ;
	for ( var propName in this.m_props )
	{
		prop = this.m_props[ propName ];
		if ( prop )
	    {
			destList.m_props[ propName ] = prop.copy();
	    }
	}
	// now copy the sublists
	var subList ;
	for ( var subListName in this.subLists() )
	{
		subList = this.subList( subListName );
		if ( subList )
		{
			destList.addSubList( subList.createNew( subList.m_name, true ), "added" );
		}
	}
}	// PropList.prototype.copy ()
PropList.prototype.initProps = function ( initVals, propSet )
{
	if ( propSet )
	{

		var configName, pObj, pTempObj ;
		for ( var i = 0; i < propSet.length; i++ )
		{
			if ( propSet[ i ] )
			{
				if ( propSet[ i ].m_type == "Property" )
				{
					//copy attributes from template object
					pObj = propSet[i].copy();
					configName = pObj.configName();
					if ( initVals && (initVals[ configName ] != null ))
					{
						pObj.value = initVals[ configName ];
					}
					else
					{
						pObj.value = "" ;
					}
					// assign default value
					pObj.setDefault();

					this.m_props[ pObj.name ] = pObj ;
				}
				else if ( propSet[i].m_type == "List" )
				{
					var subList = propSet[i].createNew( propSet[i].m_name, true );
					// reset "added" status - this sublist was created from serverData
					subList.m_status = null;
					this.addSubList( subList );
					if ( initVals && initVals[ subList.m_name ] )
					{
						// grab server data for any this lists props
						if ( subList.m_propSet )
						{
							subList.initProps( initVals[ subList.m_name ], subList.m_propSet );
						}
						// if the sublist has 'anonymous' sublists - create and initialize them from serverdata
						if ( subList.m_propSetSubLists )
						{
							subList.initSubLists( initVals[ subList.m_name ], subList.m_propSetSubLists );
						}
					}
				}
			}
		}
	}
}	// initProps
PropList.prototype.initSubLists = function ( initVals, propSet )
{
	var oThis = this;
	function enumFunc ( a, prop, curDepth )
	{ 
		if ( curDepth > 0 ) return ;
		if ( typeof( a[ prop ] ) == "object" )
		{
			if ( ! oThis.subList( prop ) )
			{

				oThis.addSubList( new PropList( prop, a[ prop ], propSet ) );
			}
		}
	}
	this.m_propSetSubLists = propSet ;
	if ( initVals )
		enumPropArray( initVals, enumFunc, 1 );
}	// initSubLists ()
PropList.prototype.toString = function () { return this.m_name; }
PropList.prototype.addSubList = function ( subList, status )
{

	this.m_subLists[ subList.m_name ] = subList ;
	subList.m_parent = this ;
	if ( status )
	{
		subList.m_status = status ;
	}
}	// addSubList ()
PropList.prototype.copySubList = function ( srcName, copyName )
{
	if ( ( srcName && copyName ) && ( srcName != copyName ) )
	{
		var srcSubList = this.m_subLists[ srcName ];
		if ( srcSubList )
		{
			this.addSubList( srcSubList.createNew( copyName, true ), "added" );
		}
	}
}
PropList.prototype.delSubList = function ( subList )
{
	if ( this.m_subLists[ subList.m_name ] )
		delete this.m_subLists[ subList.m_name ];
}
PropList.prototype.renameSubList = function ( oldName, newName )
{
	var subList = this.m_subLists[ oldName ];
	if ( subList )
	{

		this.m_subLists[ newName ] = subList ;
		delete this.m_subLists[ oldName ];
		subList.m_name = newName ;
		if ( subList.m_status == null )
		{
			subList.m_oldName = oldName ;
			subList.m_status = "edited" ;
		}
	}
}
PropList.prototype.props = function ( )
{
	return this.m_props ;
}
PropList.prototype.subLists = function ( )
{
	return this.m_subLists ;
}
PropList.prototype.parentList = function ()
{
	return this.m_parent ;
}
PropList.prototype.subListCount = function ( )
{
	var count = 0 ;
	for ( var subListName in this.m_subLists )
	{
		var subList = this.subList( subListName );
		if ( subList && (subList.m_status != "deleted" ))
			count++ ;
	}
	return count ;
}
PropList.prototype.propNames = function ( )
{
	var a = new Array();
	for ( var p in this.m_props )
		a[ a.length ] = p ;
	return a.sort();
}
PropList.prototype.listNames = function ( )
{
	var a = new Array();
	for ( var p in this.m_subLists )
		a[ a.length ] = p ;
	return a.sort();
}
PropList.prototype.prop = function ( propName )
{
	return this.m_props[ propName ];
}
PropList.prototype.subList = function ( subListName )
{
	return this.m_subLists[ subListName ];
}
PropList.prototype.value = function ( propName )
{
	return this.m_props[ propName ].value ;
}
PropList.prototype.SetRestartOnDelete = function ( flag )
{
	this.m_bRestartOnDelete = flag ;
}
PropList.prototype.needRestart = function ( fRestart )
{
	if ( fRestart != null )
		this.m_bRestart = fRestart;
	return this.m_bRestart ;
}
PropList.prototype.reset = function ()
{
	var prop ;

	for ( var propName in this.m_props )
	{
		prop = this.m_props[ propName ];
		if ( prop )
	    {
			if ( ! prop.setDefault() )
			{
				prop.ctrlValue = null ;
				prop.dirty = false ;
			}

	    }
	}
	// now do the same to any sublists
	var subList ;
	var tempSubLists = [];
	for ( var subListName in this.subLists() )
	{
		subList = this.subList( subListName );
		if ( subList )
		{
			switch ( subList.m_status )
			{
				case "added":	// don't add to templist
					break ;
				case "edited" :	// undo
					tempSubLists[ subList.m_oldName ] = subList ;
					subList.m_name = subList.m_oldName ;
					subList.m_oldName = null ;
					subList.m_status = null ;
					break ;
				case "deleted" :	// revive
					tempSubLists[ subListName ] = subList ;
					subList.m_status = null ;
					// fall through to default case
				default :
				{
					tempSubLists[ subListName ] = subList ;
					subList.reset();
					break ;
				}
			}
		}
	}
	delete this.m_subLists;
	this.m_subLists = tempSubLists;
}	// PropList.prototype.reset ()
// sets property values to the same as the control value
PropList.prototype.commit = function ()
{

	var ctrl, prop ;
	this.m_oldName = "" ;
	this.m_status = null ;
	this.m_bRestart = false ;
	this.m_bRestartOnDelete = false ;
	// commit this list's properties
	for ( var propName in this.m_props )
	{

		prop = this.m_props[ propName ];
		if ( prop && prop.dirty )
		{

			prop.value = prop.ctrlValue ;
			prop.dirty = false ;
		}
	}
	// now commit any sub lists recursivly
	var subList ;
	for ( var subListName in this.subLists() )
	{
		// reset /edited/added/deleted
		subList = this.subList( subListName );
		if ( subList.m_status == "deleted" )
		{
			this.delSubList( subList );
		}
		else
		{
			subList.commit();
		}
	}	
}	// PropList.prototype.commit ()
PropList.prototype.fillForm = function ( form )
{
	var value, ctrl, prop ;
	for ( var name in this.m_props )
	{
		prop = this.m_props[ name ];
		ctrl = prop ? prop.getCtrl( form ) : null ;
		if ( ctrl )
	    {
			prop.dirty = ( (!isBlank( prop.ctrlValue )) && ((prop.ctrlValue + "") != prop.value) );
			value = ( prop.dirty ) ? prop.ctrlValue : prop.value ;
			if ( (value == null) || (typeof( value ) == "undefined") )
				value = "" ;
			setCtrlValue( ctrl, value, true );

	    }
	}
}	// PropList.prototype.fillForm ()
PropList.prototype.clearForm = function ( form )
{
	var ctrl, prop ;
	for ( var propName in this.m_props )
	{
		prop = this.m_props[ propName ];
		ctrl = prop ? prop.getCtrl( form ) : null ;
		if ( ctrl )
	    {
			switch( ctrl[ "type" ] )
			{
				case "text" :
				case "textarea" :
				case "hidden" :
				case "password" :
				    ctrl.value = "" ;
					break;
				/* select the original default option
				*/
				case "select-one" :
				case "select-multiple" :
				{
					var selX = 0;
					for ( var x = 0; x < ctrl.options.length; x++ )
					{
					    if ( ctrl.options[x][ "defaultSelected" ] )
						{
							selX = x ;
							break ;
						}
					}
		 			ctrl.selectedIndex = selX ;
					break;
				}
				case "checkbox":
					ctrl.checked = ctrl.defaultChecked;
					break;
				default:
					break ;
			}	// switch( ctrl.type )
	    }
	}
}	// PropList.prototype.clearForm ()
// fill a select list with the names of this list's sublists, paying
// attention to each subList's status
// use the callback function to alter/filter the text that is aded to the
// list (pass back "" from the callback to skip that element
// if the propName parameter is passed, that property value of the sublist 
// will be used instead of the sublist name
// note: elements are placed into the list in sorted order
PropList.prototype.fillSelectList = function ( selectCtrl, callBackFunc, propName )
{
	var aTmp = new Array();
	var subList, listText ;
	for ( var subListName in this.subLists() )
	{
		listText = subListName ; 
		// check /edited/added/deleted status
		subList = this.subList( subListName );
		switch ( subList.m_status )
		{
			case "deleted" :	// skip
				break ;
			default :
			{
				if ( propName )
				{
					prop = subList.prop( propName );
					if ( prop )
						listText = prop.getValue();
				}
				if ( callBackFunc )
					listText = callBackFunc( listText ); 
				// Netscape treats "0" as == ""
				if ( (listText+"") != "" )
					aTmp[ aTmp.length ] = listText ;
				break ;
			}
		}
	}
	// using a sort function can make netscape crash
	if ( isIE )
		aTmp.sort( sortCaseInsensitive );
	else
		aTmp.sort();
	for ( var i = 0; i < aTmp.length; i++ )
	{
		selAddOption( selectCtrl, aTmp[ i ] );
	}
}	// PropList.prototype.fillSelectList ()
// fill a select list with the names of this list's sublists, paying
// attention to each subList's status
// use the callback function to alter/filter the text that is aded to the
// list (pass back "" from the callback to skip that element
// if the propName parameter is passed, that property value of the sublist 
// will be used instead of the sublist name
// note: elements are placed into the list in sorted order
PropList.prototype.fillSelectList2 = function ( selectCtrl, propName )
{
	var aTmp = new Array();
	var subList, listText ;
	for ( var subListName in this.subLists() )
	{
		// check /edited/added/deleted status
		subList = this.subList( subListName );
		switch ( subList.m_status )
		{
			case "deleted" :	// skip
				break ;
			default :
			{
				// Netscape treats "0" as == ""
				if ( (subListName+"") != "" )
					aTmp[ aTmp.length ] = subListName ;
				break ;
			}
		}
	}
	// using a sort function can make netscape crash
	if ( isIE )
		aTmp.sort( sortCaseInsensitive );
	else
		aTmp.sort();
	var optVal, optText;
	for ( var i = 0; i < aTmp.length; i++ )
	{
		optVal = optText = aTmp[ i ];
		if ( propName )
		{
			subList = this.subList( optVal );
			if ( subList )
			{
				var prop = subList.prop( propName );
				if ( prop )
					optText = prop.getValue();
			}
		}

		selAddOption( selectCtrl, optText, optVal );
	}
}	// PropList.prototype.fillSelectList2 ()
PropList.prototype.buildParamString = function ( rootPropPath, fForce, paramCallbackFunc )
{
	// fForce tells us wether to add properties, even if they're unchanged
	// perhaps because the listname changed
	if ( rootPropPath == null ) rootPropPath = "" ;
	var paramStr, qStr = "" ;
	// build up queryString from changed properties
	for ( var propName in this.props() )
	{
		prop = this.prop( propName );
	    if ( prop && ( fForce || prop.dirty ) )
		{
			paramStr = buildParam( rootPropPath + prop.configName(), prop.getValue() );
			if ( paramCallbackFunc )
			{
				paramStr = paramCallbackFunc( this, prop, paramStr );
			}
			qStr += paramStr ;
			this.m_bRestart |= prop.restartOnChange();
		}
	}
	// now burrow through the sublists and get their properties
	var fForceSubList ;
	for ( var subListName in this.subLists() )
	{
		fForceSubList = fForce ;
		// reset /edited/added/deleted
		subList = this.subList( subListName );
		// build up queryString from changed properties and add/deleted lists
		switch ( subList.m_status )
		{
			case "deleted" :	// delete the list
			{
				paramStr = buildParam( rootPropPath + subListName, "" );
				if ( paramCallbackFunc )
				{
					paramStr = paramCallbackFunc( subList, null, paramStr );
				}
				// list deletions go at the head of the list, in case a previous list is being set with that name
				qStr = paramStr + qStr ;
				this.m_bRestart |= subList.m_bRestartOnDelete ;
				break ;
			}
			case "edited" :		// delete the old list name...
			{
				paramStr = buildParam( rootPropPath + subList.m_oldName, "" );
				if ( paramCallbackFunc )
				{
					paramStr = paramCallbackFunc( subList, null, paramStr );
				}
				// list deletions go at the head of the list, in case a previous list is being set with that name
				qStr = paramStr + qStr ;
	 			// ...and fall through to added status
			}
			case "added":	// add every property
				fForceSubList = true ;
	 			// ...and fall through to default status
			default:
				qStr += subList.buildParamString( rootPropPath + subListName + ".", fForceSubList, paramCallbackFunc );
				break ;
		}
		this.m_bRestart |= subList.m_bRestart ;
	}

	return qStr ;
}	// PropList.prototype.buildParamString ()
PropList.prototype.isDirty = function ()
{

 	if ( this.m_status != null )
		return true ;
	var prop ;
	for ( var propName in this.m_props )
	{
		prop = this.m_props[ propName ];
		if ( prop && prop.isDirty() )
		{

			return true ;
		}
	}
	var subList ;
	for ( var subListName in this.subLists() )
	{
		subList = this.subList( subListName );
		if ( subList && subList.isDirty() )
			return true ;
	}
	return false ;
}
PropList.prototype.isNameDirty = function ()
{
	return ( (this.m_status == "added") || 
		     (this.m_status == "edited") );
}
PropList.prototype.isFormDirty = function ( form, fNoRecurse )
{

	var ctrl, prop ;
	for ( var propName in this.m_props )
	{
		prop = this.m_props[ propName ];
		ctrl = prop ? prop.getCtrl( form ) : null ;
		if ( ctrl )
	    {
			if ( isBlank( prop.value ) )
			{
				if ( getCtrlValue( ctrl ) != prop.defValue )
				{

					return true ;
				}
			}
			else if ( getCtrlValue( ctrl ) != prop.value )
			{

				return true ;
			}
	    }
	}
	if ( ! fNoRecurse )
	{
		for ( var subListName in this.subLists() )
		{
			var subList = this.subList( subListName );
			if ( subList && subList.isFormDirty( form ) )
				return true ;
		}
	}
	return false ;
}
// optionally set fIgnoreErrors to true to ignore validation errors 
// and complete recording and validation of all properties
// the first invalid ctrl will still be returned
PropList.prototype.validate = function ( form, fIgnoreErrors )
{

	// clear errMsg
	this.errMsg = "" ;
	var fChg = false ;
	var firstBadCtrl = null ;
	var propName, ctrl, prop, value ;
	for ( propName in this.m_props )
	{
		prop = this.m_props[ propName ];
		ctrl = prop ? prop.getCtrl( form ) : null ;
		if ( ctrl )
	    {
			value = getCtrlValue( ctrl );
			if ( value == "" )
			{
				if ( prop.isRequired() )
				{
					this.errMsg = "需要输入 '" + prop.label + "'." ;
					if ( ! fIgnoreErrors ) 
						return ctrl ;
					else if ( ! firstBadCtrl )
						firstBadCtrl = ctrl ;
				}
			}
			else
			{
				if ( (prop.max != null) || (prop.min != null) )
				{
					var n = parseInt( value );
					if ( ( (prop.max != null) && (prop.min != null) ) &&
						 ( isNaN( n ) || ( (n > prop.max) || (n < prop.min) ) ) )
					{
						this.errMsg = prop.label + " 必须在 " + prop.min + " 和 " + prop.max + "之间." ;
					}
					else if ( (prop.max != null) && (n > prop.max) )
					{
						this.errMsg = prop.label + " 不能超过 " + prop.max + "." ;
					}
					else if ( (prop.min != null) && (n < prop.min) )
					{
						this.errMsg = prop.label + " 不能小于 " + prop.min + "." ;
					}
					if ( this.errMsg )
					{
						if ( ! fIgnoreErrors ) 
							return ctrl ;
						else if ( ! firstBadCtrl )
							firstBadCtrl = ctrl ;
					}
				}
				//pass to custom validation rule
				if ( prop.rule )
				{
					var fValid = true ;
					if ( ( typeof( prop.rule ) == "string" ) && 
					 	 ( typeof( this[ prop.rule + "_validate" ] ) == "function" ) )
					{
						 fValid = this[ prop.rule  + "_validate" ]( value, prop, ctrl );
					}
					else if ( typeof( prop.rule ) == "function" )
					{
						 fValid = prop.rule( value, prop, ctrl );
						 // TODO how would a custom function set the errmsg?
					}
					if ( ! fValid )
					{
						if ( ! fIgnoreErrors ) 
						{
							return ctrl ;
						}
						else if ( ! firstBadCtrl )
						{
							firstBadCtrl = ctrl ;
						}
					}
				}
				//pass to custom massage rule
				if ( prop.rule )
				{
					if ( ( typeof( prop.rule ) == "string" ) && 
					 	 ( typeof( this[ prop.rule + "_massage" ] ) == "function" ) )
					{
						 value = this[ prop.rule + "_massage" ]( value );
						 setCtrlValue( ctrl, value );
					}
				}
			}
			// if we're here we have valid input: ctrl's value if new
			// NOTE: Netscape evaluate the following to be true: ("" == 0)
			// WORKAROUND: add "" to value
			if ( (prop.value + "") != (value + "") )
			{
				prop.ctrlValue = value ;
				prop.dirty = true ;
			}
			else
			{
				prop.ctrlValue = null ;
				prop.dirty = false ;
			}
	    }	// if getCtrl()
	}	//for propName in m_props
	return firstBadCtrl ;
}	// PropList.prototype.validate ()
PropList.prototype.req_validate = function ( value, prop, ctrl )
{
	if ( value.trim() == "" )
	{
		if ( prop )
			this.errMsg = "需要输入 '" + prop.label + "'." ;
		else
			this.errMsg = "必须输入." ;
		return false ;
	}
	return true ;
}
// validates integers
PropList.prototype.int_validate = function ( value, prop, ctrl )
{
	// TODO seperate validate functions for negative integers (presently not used anywhere in the admin system)
	if ( ! value.match( /^[0-9]+$/ ) )
	{
		if ( prop )
			this.errMsg = prop.label + " '" + value + "' 是一个无效的数值." ;
		return false ;
	}
	return true ;
}
PropList.prototype.int_massage = function ( value )
{
	// trim leading zeros, space, etc.
	var massageValue = parseInt( value, 10 );
	return ( isNaN( massageValue ) ? value : massageValue );
}
// validates port numbers
PropList.prototype.port_validate = function ( value, prop, ctrl )
{
	// assume success
	var isValid = this.int_validate( value );
	if ( isValid )
		isValid = ( parseInt(value) <= 65535 && parseInt(value) > 0 );
	// TODO check for previously assigned ports, etc.
	// TODO is zero a valid port#?
	if ( ! isValid )
	{
		this.errMsg = "" ;
		if ( prop ) this.errMsg = prop.label + " " ;
		this.errMsg += "'" + value + "' 是无效输入. 有效的端口号必须在 1 和 65535之间." ;
	}
	return isValid ;
}
// validates Time-To-Live
PropList.prototype.TTL_validate = function ( value, prop, ctrl )
{
	// assume success
	var isValid = this.int_validate( value );
	if ( isValid )
		isValid = ( parseInt(value) <= 255 );
	if ( ! isValid )
	{
		this.errMsg = "" ;
		if ( prop ) this.errMsg = prop.label + " " ;
		this.errMsg += "'" + value + "' 是无效输入. 有效的 TTL 值必须在 0 和 255之间." ;
	}
	return isValid ;
}
// validates passwords
PropList.prototype.password_validate = function ( value, prop, ctrl )
{
	// passwords can't be only '*' characters
	if ( prop &&
		(prop.value != value) && 
		( -1 == value.search( /[^*]/ ) ) )
	{
		this.errMsg = "无效密码: '*' 不允许单一密码." ;
		return false ;
	}
	return true ;
}
// validates list names: no dots '.' !
PropList.prototype.listname_validate = function ( value, prop, ctrl )
{
	var pos = value.search( /[\."]/ );
	if ( -1 != pos )
	{
		var errChar = value.charAt( pos );
		if ( prop ) 
			this.errMsg = " 在 " + prop.label + " 域中不允许'"+ errChar + "' 特性." ;
		else
			this.errMsg = "以下 '" + errChar + "' 特性不被许可." ;
		return false ;
	}
	return true ;
}
// validates mount points: illegal URL chars
PropList.prototype.mountpoint_validate = function ( value, prop, ctrl )
{
	var badChars = "=!@#$%^&*(){}[];:'\"<>,?|~ \\" ;
	if ( ( value != "/" ) && ( -1 == value.search( "^\\/.*\\/$" ) ) )
	{
		this.errMsg = "加载点必须以正斜杠('/')来开始和结束."
		return false ;
	}
	for( var i = 0; i < value.length; i++ )
	{
		if ( -1 != badChars.indexOf( value.charAt( i ) ) )
		{
			this.errMsg = "加载点不能包含空格或者以下特殊符号: '" + badChars + "'." ;
			return false ;
		}
	}
	return true ;
}
// validates virtual directories: illegal URL chars
PropList.prototype.virdir_validate = function ( value, prop, ctrl )
{
	var badChars = "=!@#$%^&(){}[];:'\"<>,?|~ \\" ;
	if ( value != "/" ) {
		if ( 0 != value.search( "^\\/" ) )
		{
			this.errMsg = "虚拟目录必须以正斜杠('/')作为开始."
			return false ;
		} 
		if ( -1 != value.search( "\\/$" ) ) {
			this.errMsg = "虚拟目录不能以正斜杠('/')作为结束."
			return false ;
		}
	}	
	for( var i = 0; i < value.length; i++ )
	{
		if ( -1 != badChars.indexOf( value.charAt( i ) ) )
		{
			this.errMsg = "虚拟目录不能包含空格或者以下特殊符号: '" + badChars + "'." ;
			return false ;
		}
	}
	return true ;
}
// validates virtual directories: illegal URL chars
PropList.prototype.virpath_validate = function ( value, prop, ctrl )
{
	var badChars = "=!@#$%^&(){}[]:;'\"<>,?|~ \\" ;
	for( var i = 0; i < value.length; i++ )
	{
		if ( -1 != badChars.indexOf( value.charAt( i ) ) )
		{
			this.errMsg = "路径名不能包含空格或者以下特殊符号:  '" + badChars + "'." ;
			return false ;
		}
	}
	return true ;
}
// validates virtual directories: illegal URL chars
PropList.prototype.url_validate = function ( value, prop, ctrl )
{
	var badChars = "$(){}[];'\"<>,| \\" ;
	for( var i = 0; i < value.length; i++ )
	{
		if ( -1 != badChars.indexOf( value.charAt( i ) ) )
		{
			this.errMsg = "URL地址不能包含空格或者以下特殊符号:  '" + badChars + "'." ;
			return false ;
		}
	}
	return true ;
}
// validates mount points: illegal URL chars
PropList.prototype.mimetype_validate = function ( value, prop, ctrl )
{
	var badChars = "=!@#$%^&(){}[];:'\"<>,?|~ \\" ;
	if ( -1 == value.search( /^[^\/]+\/[^\/]+$/ ) )
	{
		this.errMsg = "MIME 类型必须包含在 类型/子类型 表中."
		return false ;
	}
	for( var i = 0; i < value.length; i++ )
	{
		if ( -1 != badChars.indexOf( value.charAt( i ) ) )
		{
			this.errMsg = "MIME 类型不能包含空格或者以下特殊符号: : '" + badChars + "'." ;
			return false ;
		}
	}
	return true ;
}
// validates mount points: illegal URL chars
PropList.prototype.ext_validate = function ( value, prop, ctrl )
{
	var badChars = ".=!@#$%^&(){}[];:'\"<>,?|~ \\" ;
	for( var i = 0; i < value.length; i++ )
	{
		if ( -1 != badChars.indexOf( value.charAt( i ) ) )
		{
			this.errMsg = "MIME 类型扩展不能包含空格或者以下特殊符号:  '" + badChars + "'." ;
			return false ;
		}
	}
	return true ;
}
PropList.prototype.hostname_validate = function ( value, prop, ctrl )
{
	// if it looks like an addr but its invalid call foul
	if ( isIPAddrFormat( value ) && (!isIPAddr( value )) )
	{
		this.errMsg = "IP 地址必须在以下范围 [1-255].[0-255].[0-255].[0-255]" ;
		return false;
	}
	// else see if we have a correctly formed hostname
	var a = value.match( /^[^. =!@#$%^&(){}[\];:'"<>,?|~\\]+(\.[^. =!@#$%^&(){}[\];:'"<>,?|~\\]+)*$/ );
	if ( a == null )
	{
		this.errMsg = "无效的主机名: '" + value + "'." ;
		return false ;
	}
	return true ;
}
PropList.prototype.ip_validate = function ( value, prop, ctrl )
{
	var isValid = isIPAddr( value );
	if ( ! isValid )
		this.errMsg = "IP 地址必须在以下范围 [1-255].[0-255].[0-255].[0-255]" ;
	return isValid ;
}
// allows an address of 0.0.0.0
PropList.prototype.ipzero_validate = function ( value, prop, ctrl )
{
	var isValid = isIPAddr( value, true );
	if ( ! isValid )
		this.errMsg = "IP 地址必须在以下范围 [0-255].[0-255].[0-255].[0-255]" ;
	return isValid ;
}
// accepts an IP address (including 0.0.0.0) and "any" or "localhost"
PropList.prototype.ipwildcard_validate = function ( value, prop, ctrl )
{
	if ( isIPAddr( value ) || 
		(value.toLowerCase() == "any") ||
		(value.toLowerCase() == "localhost") )
	{
		return true ;
	}
	else
	{
		this.errMsg = 'IP 地址必须在以下范围  [0-255].[0-255].[0-255].[0-255] 或者\n 用"Any" 来描述所有的IP地址.' ;
		return false ;
	}
}
PropList.prototype.netmask_validate = function ( value, prop, ctrl )
{
	value = value.trim();
	if ( this.ip_validate( value ) || ( (parseInt( value ) >= 0) && (parseInt( value ) <= 32) ) )
	{
		return true ;
	}
	else
	{
		this.errMsg = "子网掩码必须在以下范围 [0-255].[0-255].[0-255].[0-255] 或者是一个 0 到 32 (包含)之间的整数" ;
		return false ;
	}
}
PropList.prototype.string_validate = function ( value, prop, ctrl )
{
	var badChars = "'\"" ;
	for( var i = 0; i < value.length; i++ )
	{
		if ( -1 != badChars.indexOf( value.charAt( i ) ) )
		{
			this.errMsg = "请输入其他名字,同时保证不包含以下特殊符号:  '" + badChars + "'." ;
			return false ;
		}
	}
	return true ;
}
PropList.prototype.uid_massage = function ( value )
{
	// if the value is all numbers but not preceeded by a '%' then prepend one
	if ( value == "-1" || this.int_validate( value ) )
	{
		return "%" + this.int_massage( value ) ;
	}
	return value ;
}
PropList.prototype.uid_validate = function ( value, prop, ctrl )
{
	// if '%' is the first character then the following text must by an int or -1
	if ( ( value == "%-1" ) || 
		 this.int_validate( value ) ) // "%" will be appended by uid_massage()
		return true ;
	var badIDErrMsg = '用户和用户组标识必须在 "%-1" 或 "%<ID Number>", 或用户名 或组列表当中.' ;
	if ( value.indexOf( '%' ) == 0 )
	{
		if ( this.int_validate( value.substr( 1 ) ) )
		{
			return true ;
		}
		else
		{
			this.errMsg = badIDErrMsg ;
			return false ;
		}
	}
	// else do a crude syntax check of the name
	if ( -1 == value.search( /^[A-Z][A-Z_0-9]*$/i ) )
	{
		this.errMsg = "错误的用户 或 组名" ;
		return false ;
	}
	return true ;
}
PropList.prototype.hostwildcard_validate = function ( value, prop, ctrl )
{
	if ( value )
	{
		// one '*' wildcard per host
		var a = value.match( /\*/g );
		if ( a && a.length > 1 )
		{
			this.errMsg = "只允许一个通配符 ('*') ." ;
			return false ;
		}
	}
	return true ;
}
// this function is used by the localFS and and ad-serving pages to
// detect if a "Description" is unique among all other FSMount listnames
// it depends on the FSMountListNames array (emitted by the server-side
// funcion emitFSMountListNames()) and the existence of a Description property
// in the passed subList
PropList.prototype.checkDescriptionUniqueness = function ( extList, ctrlDescription )
{
	var isUnique = true ;
	var descProp = this.prop( "Description" );
	if ( descProp && descProp.dirty )
	{
		var thisDesc = descProp.getValue();
		if ( extList && extList[ thisDesc ] )
		{
			isUnique = false ;
		}
		else if ( this.parentList() )
		{
			// loop through the other sublists looking for other changed descriptions that match this one
			// these lists may not be reflected in the snaphot of listsnames in FSMountListNames
			for ( var virDir in this.parentList().subLists() )
			{
				var otherList = this.parentList().subList( virDir );
				if ( otherList && ( this != otherList ) )
				{
					var otherProp = otherList.prop( "Description" );
				    if ( otherProp && ( thisDesc == otherProp.getValue() ) )
					{
						isUnique = false ;
						break ;
					}
				}
			}	
		}
	}
	if ( isUnique )
	{
		return true ;
	}
	else
	{
		return rejectInput( ctrlDescription, 
			"加载点的描述必须是唯一的. " + 
			qVar( getCtrlValue( ctrlDescription ) ) + 
			" 已被别处的加载点的描述." );
	}
}

// this function is used by the localFS and and ad-serving pages to
// detect if a "Description" is unique among all other FSMount listnames
// it depends on the FSMountListNames array (emitted by the server-side
// funcion emitFSMountListNames()) and the existence of a Description property
// in the passed subList
PropList.prototype.checkListNameUniqueness = function ( extList, listName )
{
	var isUnique = true ;
	if ( extList && extList[ listName ] )
	{
		isUnique = false ;
	}
	if ( isUnique && this.parentList() )
	{
		// loop through the other sublists looking for other changed descriptions that match this one
		// these lists may not be reflected in the snaphot of listnames in FSMountListNames
		for ( var otherListName in this.parentList().subLists() )
		{
			var otherList = this.parentList().subList( otherListName );
			if ( otherList && ( this != otherList ) )
			{
			    if ( listName == otherListName )
				{
					isUnique = false ;
					break ;
				}
			}
		}	
	}
	if ( !isUnique )
	{
		this.errMsg=
			"描述必须是唯一的. " + 
			qVar( listName ) + 
			" 已被用于别处的描述." ;
	}
	return isUnique ;
}

// this function is used by the localFS and and ad-serving pages to
// detect if a "Description" is unique among all other FSMount listnames
// it depends on the FSMountListNames array (emitted by the server-side
// funcion emitFSMountListNames()) and the existence of a Description property
// in the passed subList
PropList.prototype.checkMounPointUniqueness = function ( extList, description, mountPoint, ctrl /*optional*/ )
{
	var isUnique = true ;
	if ( extList )
	{
		for (var listName in extList)
		{
			if ( (listName != description) && (extList[ listName ] == mountPoint) ) 
			{
				isUnique = false ;
			}
		}
	}
	if ( isUnique && this.parentList() )
	{
		// loop through the other sublists looking for other changed mountpoints that match this one
		// these lists may not be reflected in the snaphot of listnames in FSMountListNames
		for ( var otherListName in this.parentList().subLists() )
		{
			var otherList = this.parentList().subList( otherListName );
			if ( otherList && ( this != otherList ) )
			{
				if ( otherList.prop( "MountPoint" ) )
				{
					var otherMountPoint = 
						otherList.prop( "MountPoint" ).getValue();
				    if ( mountPoint == otherMountPoint )
					{
						isUnique = false ;
						break ;
					}
				}
			}
		}	
	}
	if ( (!isUnique) && ctrl)
	{
		return rejectInput( ctrl, 
			"加载点必须是唯一的. " + 
			qVar( mountPoint ) + 
			" 已被用于别处.");
	}
	return isUnique ;
}
PropList.prototype.propSearch = function ( propNameExp, callback )
{
 	if ( this.m_status == "deleted" )
		return ;
 	for ( var name in this.m_props )
	{
		if ( -1 != name.search( propNameExp ) )
		{
			if ( callback( this, this.m_props[ name ] ) )
			{
				return 1;
			}
		}
	}
	for ( var listName in this.m_subLists )
	{
		var subList = this.subList( listName );
		if ( subList ) 
		{
			if ( subList.propSearch( propNameExp, callback ) )
			{
				return 1;
			}
		}
	}
}
PropList.prototype.setFormDefaults = function ( form )
{
	var ctrl, prop ;
	for ( var propName in this.m_props )
	{
		prop = this.m_props[ propName ];
		ctrl = ( prop ? prop.getCtrl( form ) : null );
		if ( ctrl )
	    {
			setCtrlValue( ctrl, prop.defValue );
	    }
	}	//for propName in m_props
}	// PropList.prototype.setFormDefaults ()
PropList.prototype.resetToDefaults = function ( form )
{
	var ctrl, prop ;
	for ( var propName in this.m_props )
	{
		prop = this.m_props[ propName ];
		ctrl = ( prop ? prop.getCtrl( form ) : null );
		if ( ctrl && (prop.defValue != "") )
	    {
			setCtrlValue( ctrl, prop.defValue );
	    }
	}	//for propName in m_props
}	// PropList.prototype.resetToDefaults ()

